package com.github.geequery.springdata;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.persistence.OptimisticLockException;

import org.easyframe.enterprise.spring.CommonDao;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;

import com.github.geequery.springdata.config.PersistenceContext;
import com.github.geequery.springdata.config.PersistenceContext2;
import com.github.geequery.springdata.repository.support.Update;
import com.github.geequery.springdata.test.entity.ComplexFoo;
import com.github.geequery.springdata.test.entity.Foo;
import com.github.geequery.springdata.test.entity.VersionLog;
import com.github.geequery.springdata.test.repo.ComplexFooDao;
import com.github.geequery.springdata.test.repo.FooDao;
import com.github.geequery.springdata.test.repo.FooEntityDao;
import com.github.geequery.springdata.test2.repo.FooDao2;

import jef.database.ORMConfig;
import jef.database.QB;
import jef.database.RecordsHolder;
import jef.database.query.Query;

/**
 * 与Spring集成的示例。 本示例使用的xml作为Spring配置。参见
 * src/main/resources/spring/spring-test-case1.xml
 * 
 * 4、MySQL下的时间精度造成版本无效 (修复)
 * 
 * @author jiyi
 * 
 */
@ContextConfiguration(classes = { PersistenceContext.class,PersistenceContext2.class })
public class Case2 extends AbstractJUnit4SpringContextTests implements InitializingBean {

    @javax.annotation.Resource
    private CommonDao commonDao;

    @javax.annotation.Resource
    private FooDao foodao;

    @javax.annotation.Resource
    private FooEntityDao foodao2;

    @javax.annotation.Resource
    private ComplexFooDao complex;

    @Resource
    private FooDao2 foodaoM;

    @Test
    public void testCase1() {
        Foo foo = new Foo();
        foo.setAge(1);
        foo.setName("咋呼呼");
        foodao.save(foo);

        foodaoM.save(foo);

    }

    /**
     * 使用方法名来定义查询的案例.
     * 
     * @throws SQLException
     */
    @Test
    public void testFooDAO() throws SQLException {
        // =============== 单字段查找 ==========
        {
            System.out.println("=== FindByName ===");
            Foo foo = foodao.findByName("张三");
            System.out.println(foo.getName());
            System.out.println(foo.getId());
        }
        // =============查找，带排序============
        {
            System.out.println("=== FindByAgeOrderById ===");
            List<Foo> fooList = foodao.findByAgeOrderById(0);
            System.out.println(fooList);

            int id = fooList.get(0).getId();
            // =============== 使用悲观锁更新 ==================
            boolean updated = foodao.lockItAndUpdate(id, new Update<Foo>() {
                @Override
                public void setValue(Foo value) {
                    value.setName("李四");
                    value.setRemark("悲观锁定");

                }
            });
            if (updated) {
                Foo u = foodao.getOne(id);
                Assert.assertEquals("悲观锁定", u.getRemark());
            }
        }
        // ==============使用分页，固定排序===========
        {
            System.out.println("=== FindByAge Page ===");
            Page<Foo> fooPage = foodao.findByAgeOrderById(0, new PageRequest(1, 4));
            System.out.println(fooPage.getTotalElements());
            System.out.println(Arrays.toString(fooPage.getContent().toArray()));
        }
        // ==============分页+传入排序参数===========
        {
            System.out.println("=== FindAll(page+sort) ===");
            Page<Foo> p = foodao.findAll(new PageRequest(0, 3, new Sort(new Order(Direction.DESC, "age"))));
            System.out.println(p.getTotalElements());
            System.out.println(Arrays.toString(p.getContent().toArray()));
        }

        // ===================不分页，传入排序参数===========================
        {
            System.out.println("=== FindAll(sort) ===");
            Iterable<Foo> iters = foodao.findAll(new Sort(new Order(Direction.DESC, "id")));
            System.out.println("list=" + iters);
        }
        // ===========查询多个ID的记录==============
        {
            System.out.println("=== FindAll(?,?,?) ===");
            List<Integer> id = Arrays.<Integer> asList(1, 3, 4, 5);
            Iterable<Foo> foos = foodao.findAllById(id);
            System.out.println("list=" + foos);
        }
        {
            // =========== 在方法中携带运算符 like ===========
            System.out.println("=== countByNameLike ===");
            System.out.println(foodao.countByNameLike("%四"));
            System.out.println("=== findByNameLike ===");
            List<Foo> foo = foodao.findByNameLike("%四");
            System.out.println(foo);
        }

        {
            // 在方法中携带运算符 contains，并加上另一个条件
            // 如果入参顺序和方法名中的一致，可以不加注解
            System.out.println("=== findByNameContainsAndAge ===");
            List<Foo> foos = foodao.findByNameContainsAndAge("李", 0);
            System.out.println(foos);
        }

        {
            // 多参数顺序测试否有问题
            System.out.println("=== findByNameStartsWithAndAge ===");
            List<Foo> foos = foodao.findByNameStartsWithAndAge(0, "李");
            System.out.println(foos);
        }
        {
            // 删除全部
            System.out.println("=== DeleteAll() ===");
            foodao.deleteAll();
        }
    }

    @Test
    public void testAbc() {
        {
            /**
             * 最基本的@Query查询，注意需要@Param来绑定参数
             */
            Foo foo = foodao2.findBysName("张三");
            System.out.println(foo);
        }
    }

    @Test
    public void testFooDao2() {
        {
            /**
             * 最基本的@Query查询，注意需要@Param来绑定参数
             */
            Foo foo = foodao2.findBysName("张三");
            System.out.println(foo);
        }
        {
            /**
             * Like查询，不使用@Param来绑定
             */
            Foo foo = foodao2.findByusername("张");
            System.out.println(foo);
        }
        // =========================
        {
            /**
             * 1 @Query(name='xxx')可以从预定义的命名查询中获得一个配置好的查询语句 2 如果使用 @Param 对应
             * :name，那么方法的参数先后顺序可以随意修改。反之，如果是 ?1 ?2方式进行参数绑定，则方法参数顺序有要求。
             */
            //
            List<Foo> foos = foodao2.findBySql(new Date(), "李四").collect(Collectors.toList());
            System.out.println(foos);
        }
        // =========================
        {
            /**
             * 没有设置@param参数时， ?1 ?2 来分别表示第一个和第二个参数。此时方法参数顺序有要求。
             */
            List<Foo> foos = foodao2.findBySql2("李四", new Date());
            System.out.println(foos);

        }
        {
            /**
             * 单纯的Like运算符不会在查询子条件 李四上增加通配符。因此需要自己传入通配符 %李%
             */
            System.out.println("=== findBySql3() ====");
            Foo foo = foodao2.findBySql3("李", 0);
            System.out.println(foo);
        }
        {
            /**
             * 用?1 ?2绑定时，顺序要注意。 如果在SQL语句中指定LIKE的查询方式是 ‘匹配头部’，那么查询就能符合期望
             */
            System.out.println("=== findBySql4() ====");
            Foo foo = foodao2.findBySql4(0, "李");
            System.out.println(foo);
        }
        {
            /**
             * 1、SQL语句查询支持分页功能 2、传入null可以表示忽略对应的查询条件吗（动态SQL）? 默认情况下是不可以的，但是使用
             * 
             * @IgnoreIf()注解可以化不可能为可能
             */
            Page<Foo> page = (Page<Foo>) foodao2.findBySql5(0, null, new PageRequest(1, 4));
            System.out.println(page.getTotalElements());
            System.out.println(page.getContent());
        }
        {
            /**
             * 1、like <$string$>的用法 2、顺序不能用Spring-data的方法传入并使用。 但是可以换一种方法
             */
            System.out.println("=== findBySql6() ====");
            List<Foo> result = foodao2.findBySql6(0, "张", new Sort(new Order(Direction.DESC, "id")));
            System.out.println(result);

            System.out.println("=== findBySql6-2() ====");
            result = foodao2.findBySql62(0, "张", "id desc");
            System.out.println(result);
        }
        {
            /**
             * 1、条件自动省略 2、如果条件中带有 % _等特殊符号，会自动转义
             */
            Page<Foo> page = foodao2.findBySql7(0, "李%", new PageRequest(3, 5));
            System.out.println(page.getContent());

            page = foodao2.findBySql7(0, "", new PageRequest(3, 5));
            System.out.println(page.getContent());
        }
        {
            /**
             * 使用SQL语句插入记录
             */
            int ii = foodao2.insertInto("六河", 333, "测试", new Date());
            System.out.println(ii);
            ii = foodao2.insertInto2("狂四", 555, "测试", new Date());
            System.out.println(ii);
        }
        {
            /**
             * 使用SQL语句来update
             */
            int ii = foodao2.updateFooSetAgeByAgeAndId(new Date(), 12, 2);
            System.out.println(ii);
        }
    }

    @Test
    public void tetete() {
        foodao2.insertInto("李四", 100, "111", new Date());
        List<String> foos = foodao2.findBySql(new Date(), "李四").map(Foo::getName).collect(Collectors.toList());
        System.out.println(foos);
    }

    /**
     * 当有复合主键时
     */
    @Test
    public void testComplexId() {
        // 测试复合主键的情况
        {
            ComplexFoo cf = new ComplexFoo(1, 2);
            cf.setMessage("test224");
            complex.save(cf);

            cf = new ComplexFoo(2, 2);
            cf.setMessage("1222234324");
            complex.save(cf);
        }
        {
            ComplexFoo cf = complex.getOne(new int[] { 1, 2 });
            System.out.println(cf);
            cf.setMessage("修改消息!");
            complex.save(cf);
        }
        {
            Iterable<ComplexFoo> list = complex.findAllById(Arrays.asList(new int[] { 1, 2 }, new int[] { 2, 2 }));
            for (ComplexFoo foo : list) {
                System.out.println(foo);
            }
        }
        {
            complex.deleteById(new int[] { 1, 2 });
        }
    }

    /**
     * 锁案例1-1： 乐观锁
     */
    @Test(expected = OptimisticLockException.class)
    public void testVersionUpdateAndOptLock() {
        int id;
        {
            VersionLog v = new VersionLog();
            v.setName("叶问");
            commonDao.insert(v);
            id = v.getId();
        }
        {
            VersionLog v = commonDao.load(VersionLog.class, id);
            v.setName("叶问天");
            commonDao.update(v);
        }
        {
            VersionLog v = commonDao.load(VersionLog.class, id);

            VersionLog v2 = commonDao.load(VersionLog.class, id);
            v2.setName("抢先更新");
            commonDao.update(v2);
            try {
                v.setName("啥，已经被人更新了，那我不是写不进去了！");
                commonDao.update(v);
            } catch (OptimisticLockException e) {
                e.printStackTrace();
                throw e;
            }
        }

    }

    /**
     * 锁案例1-2：批量更新模式下的乐观锁
     */
    @Test
    public void testVersionAndOptLockInBatch() {
        int id;
        {
            VersionLog v = new VersionLog();
            v.setName("我的");
            commonDao.insert(v);
            id = v.getId();
        }
        {
            VersionLog v1 = commonDao.load(VersionLog.class, id);
            VersionLog v2 = commonDao.load(VersionLog.class, id - 3);
            VersionLog v3 = commonDao.load(VersionLog.class, id - 2);
            VersionLog v4 = commonDao.load(VersionLog.class, id - 1);

            VersionLog v_ = commonDao.load(VersionLog.class, id - 2);
            v_.setName("再次抢先更新");
            commonDao.update(v_);

            v1.setName("更新1");
            v2.setName("更新2");
            v3.setName("又被抢了，还能好好做朋友吗？");
            v4.setName("更新4");
            int count = commonDao.batchUpdate(Arrays.asList(v1, v2, v3, v4));
            Assert.assertEquals(3, count); // 该条记录没有更新

            v3 = commonDao.load(v3);
            Assert.assertEquals("再次抢先更新", v3.getName()); // 该条记录没有更新
            /**
             * 在Batch模式下，乐观锁可以阻止覆盖他人的记录（无法写入），但是无法检测出是哪一组记录因为冲突造成无法写入。
             * 因此只能确认不覆盖其他人的记录。
             * 
             * 此外，非按主键更新的场合下，乐观锁也不能生效。因为非按主键更新时的更新请求并不能对应要数据库的的特定记录，无法检查
             * 记录是否非修改。无法要求乐观锁进行干预。
             */
        }
    }

    /**
     * 锁案例2：悲观锁的使用
     */
    @Test
    public void testPessimisticLock() {
        int id;
        {
            VersionLog v = new VersionLog();
            v.setName("我");
            commonDao.insert(v);
            id = v.getId();
        }
        {
            Query<VersionLog> query = QB.create(VersionLog.class).addCondition(QB.between(VersionLog.Field.id, id - 4, id));
            RecordsHolder<VersionLog> records = commonDao.selectForUpdate(query);
            try {
                for (VersionLog version : records) {
                    version.setName("此时:" + version.getName());
                    // version.setModified(System.currentTimeMillis());
                }
                records.commit();
            } finally {
                records.close();
            }

        }
        {
            Query<VersionLog> query = QB.create(VersionLog.class).addCondition(QB.between(VersionLog.Field.id, id - 4, id));
            List<VersionLog> records = commonDao.find(query);
            for (VersionLog version : records) {
                System.out.println(version.getName());
            }
        }

    }

    /**
     * 锁案例3：特定场景下不需要加锁更新
     */
    @Test
    public void testUpdateWithoutLock() {
        List<Foo> foos = commonDao.find(QB.create(Foo.class));
        Foo foo = foos.get(0);
        QB.fieldAdd(foo, Foo.Field.age, 100);
        foo.setName("姓名也更新了");
        commonDao.update(foo);
    }

    /**
     * 使用自行实现的扩展方法
     */
    @Test
    public void testCustom() {
        ComplexFoo cf = new ComplexFoo(1, 2);
        complex.someCustomMethod(cf);
    }

    /**
     * 在使用Spring-data的同时，传统的commondao/Session等方式操作依然可以正常使用
     * 
     * @throws SQLException
     */
    @Test
    public void test1() throws SQLException {
        commonDao.getNoTransactionSession().dropTable(Foo.class);
        commonDao.getNoTransactionSession().createTable(Foo.class);
        {
            Foo foo = new Foo();
            foo.setName("Hello!");
            commonDao.batchInsert(Arrays.asList(foo));
        }
        {
            Foo foo = new Foo();
            foo.setAge(3);
            foo.setName("Hello!");
            // update MY_FOO set age=3 where name='Hello!'
            commonDao.updateByProperty(foo, "name");
        }
        {
            Foo foo = commonDao.loadByPrimaryKey(Foo.class, 1);
            System.out.println(foo.getName());
        }
        {
            // 根据ID删除
            commonDao.removeByField(Foo.class, "id", 1);
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        ORMConfig.getInstance().setDebugMode(false);
        commonDao.getNoTransactionSession().dropTable(Foo.class, VersionLog.class);
        commonDao.getNoTransactionSession().createTable(Foo.class, VersionLog.class);
        {
            List<Foo> list = new ArrayList<Foo>();
            list.add(new Foo("张三"));
            list.add(new Foo("李四"));
            list.add(new Foo("王五"));
            list.add(new Foo("张昕"));
            list.add(new Foo("张鑫"));
            list.add(new Foo("测试"));
            list.add(new Foo("张三丰"));
            list.add(new Foo("李元吉"));
            list.add(new Foo("李渊"));
            list.add(new Foo("李建成"));
            list.add(new Foo("李世民"));
            list.add(new Foo("赵日天"));
            list.add(new Foo("叶良辰"));
            list.add(new Foo("玛丽苏"));
            list.add(new Foo("龙傲天"));
            foodao.saveAll(list);
        }
        {
            VersionLog v1 = new VersionLog();
            v1.setName("一见钟情");
            VersionLog v2 = new VersionLog();
            v2.setName("两两相依");
            VersionLog v3 = new VersionLog();
            v3.setName("三生三世");
            VersionLog v4 = new VersionLog();
            v4.setName("四海为家");
            commonDao.batchInsert(Arrays.asList(v1, v2, v3, v4));
        }
        ORMConfig.getInstance().setDebugMode(true);
        System.out.println("============= 下面案例正式开始 ===========");
    }
}
